package Euclid;

import java.util.*;
import java.lang.reflect.*;
import java.io.*;
import java.net.*;


/**
 * Function Library, delegates calls to functions
 * 
 * @author Jon Ludwig
 *
 */
public class FunctionLibrary {
	
	private Hashtable<String, SymbVarType> vars;
	private Hashtable<String, EuclidExporter> funcs;
	
	public FunctionLibrary()
	{
		vars = new Hashtable<String, SymbVarType>();
		funcs = new Hashtable<String, EuclidExporter>();
		
		// find plugins
		try {
			File plugindir = new File("plugins");
			
			if ((plugindir.exists() == false) || (plugindir.isDirectory() == false)) {
				// no plugin dir
				System.err.println("Plugin Directory (plugins) Missing.");
			}
			else
			{
				// list packages
				File plugins[] = plugindir.listFiles(new FilenameFilter() {
					public boolean accept(File dir, String name) {
						return name.endsWith(".jar");
					}
				});
				
				for (File f : plugins)
				{
					// load plugin exporter
					try {
						PluginLoader loader = new PluginLoader(f.toURL());
						EuclidExporter c = loader.create();
						
						System.out.println("Loaded plugin: " + f.getName());
						
						// get exports
						String[] exports = loader.getExports();
						for (String m : exports) {
							funcs.put(m, c);
							System.out.println("loaded " + m);
						}
						
					} catch (MalformedURLException e) {
						// TODO Auto-generated catch block
						e.printStackTrace();
					}
				}
			}
			
		}
		catch (SecurityException e) {
			e.printStackTrace();
		}
		
	}

	/**
	 * Call a function.
	 * 
	 * @param name
	 * @param args
	 * @return
	 */
	public VarType call(String name, List<VarType> args)
	{
		VarType val = null;
			
		try {
			EuclidExporter obj = funcs.get(name);
			Class c = obj.getClass();
			Class[] paramtypes = new Class[args.size()];
			Object[] paramvals = new Object[args.size()];
			
			// set up args
			int i = 0;
			for (VarType v : args) {
				paramtypes[i] = VarType.class;
				paramvals[i] = args.get(i);
				i++;
			}
			
			// get correct method & invoke
			try {
				Method m = c.getDeclaredMethod(name, paramtypes);
				val = (VarType)m.invoke(obj, paramvals);
			} catch (NoSuchMethodException e) {
				// try the var args version
				Class[] paramtypes2 = new Class[] { VarType[].class };
				VarType[] paramvals2 = new VarType[args.size()];
				System.arraycopy(args.toArray(), 0, paramvals2, 0, args.size());
				Method m2 = c.getDeclaredMethod(name, paramtypes2);
				val = (VarType)m2.invoke(obj, (Object)paramvals2);
			}
			
			
			/*
			Class<FunctionLibrary> c = FunctionLibrary.class;
			Class[] paramtypes = new Class[args.size()];
			Object[] params = new Object[args.size()];
			
			int i = 0;
			for (VarType v: args) {
				paramtypes[i] = VarType.class;
				params[i] = args.get(i);
				i++;
			}
			
			Method f = c.getMethod(name, paramtypes);
			var = (VarType)f.invoke(null, params);
			*/
			
		} catch (Exception e) {
			e.printStackTrace();
		}
		
		return val;
	}
	
	/**
	 * Set a variable
	 * 
	 * @param name
	 * @param val
	 * @return
	 */
	public SymbVarType setvar(String name, VarType val)
	{
		SymbVarType sym = new SymbVarType(name, val);
		//sym.formula = val.toString();
		
		vars.put(name, sym);
		System.out.println(sym.formula);
		
		return sym;
	}
	
	/**
	 * Get a variable
	 * 
	 * @param name
	 * @return
	 */
	public SymbVarType getvar(String name)
	{
		SymbVarType v;
		
		v = vars.get(name);
		System.out.println("get: " + v.name);
		
		return v;
	}
	
	/**
	 * Loads plugin JARs and gets the exported methods
	 * 
	 * @author Jon Ludwig
	 */
	private class PluginLoader extends URLClassLoader
	{
		private URL url;
		private Class exportclass;
		private EuclidExporter exporter;
		
		public PluginLoader(URL url)
		{
			super (new URL[] {url});
			this.url = url;
			this.exportclass = null;
			this.exporter = null;
		}
		
		public EuclidExporter create()
		{
			try {
				exportclass = loadClass("EuclidExport");
				exporter = (EuclidExporter)exportclass.newInstance();
		
			} catch (Exception e) {
				e.printStackTrace();
			}
			
			return exporter;
		}
		
		public String[] getExports()
		{
			String[] exports = null;
			
			try {
				
				Method getexports = exportclass.getMethod("getExports", (Class[])null);
				exports = (String[])getexports.invoke(exporter, (Object[])null);
				
			} catch (Exception e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
			
			return exports;
		}
	}
}
